feat(api): add @readied/api backend for cloud sync#72
feat(api): add @readied/api backend for cloud sync#72tomymaritano wants to merge 3 commits intodevelopfrom
Conversation
Cloud sync backend for Pro tier: - Magic link authentication via email - Push/pull sync operations for encrypted notes - Stripe webhook integration for subscriptions - Drizzle ORM with Neon Postgres schema Tech stack: - Hono for edge runtime compatibility - jose for JWT handling - Zod for request validation - Cloudflare Workers deployment ready 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ea9211fe48
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const { payload } = await jose.jwtVerify(token, secret, { | ||
| algorithms: ['HS256'], | ||
| }); | ||
|
|
||
| if (!payload.sub || !payload.email) { |
There was a problem hiding this comment.
Reject refresh tokens in auth middleware
Because the middleware only checks for sub and email after jwtVerify, a refresh token (which is also HS256-signed and contains those fields) can be used as a Bearer token on protected routes. That makes the 7‑day refresh token equivalent to an access token, undermining the intended 15‑minute access token window. This occurs whenever a client (or attacker with a leaked refresh token) sends it in the Authorization header. Consider validating a token type/claim and explicitly rejecting type: 'refresh' here.
Useful? React with 👍 / 👎.
| const body = await c.req.text(); | ||
|
|
||
| // Verify webhook signature (simplified - in production use Stripe SDK) | ||
| // For now, we'll trust the signature and parse the event | ||
| // TODO: Implement proper signature verification with crypto.subtle |
There was a problem hiding this comment.
Verify Stripe webhook signatures before processing
The webhook handler only checks that the stripe-signature header exists, then parses and processes the body without validating the signature. This means any caller who can reach the endpoint can forge events to activate/cancel subscriptions or mark payments failed. This is a security/billing risk in production; you should verify the signature with the Stripe SDK (or crypto.subtle) using STRIPE_WEBHOOK_SECRET before trusting the payload.
Useful? React with 👍 / 👎.
| // Get current max version for this user | ||
| const [maxVersionResult] = await db | ||
| .select({ maxVersion: sql<number>`COALESCE(MAX(${syncLog.version}), 0)` }) | ||
| .from(syncLog) | ||
| .where(eq(syncLog.userId, userId)); |
There was a problem hiding this comment.
Prevent duplicate sync versions under concurrency
The push handler computes MAX(sync_log.version) once and then increments locally for each change. If two push requests for the same user run concurrently, they can both read the same max version and insert overlapping versions, which breaks the monotonic ordering your cursor logic relies on. This happens when multiple devices push at the same time. Consider using a database-side sequence/transaction with locking, or a unique constraint plus retry logic, to guarantee per‑user version uniqueness.
Useful? React with 👍 / 👎.
- Replace @neondatabase/serverless with @libsql/client - Update schema from pg-core to sqlite-core types - Use text columns with ISO strings for dates (SQLite compatible) - Update drizzle.config.ts for Turso dialect - Update wrangler.toml with Turso env vars Benefits of Turso: - SQLite distributed (same engine as desktop app) - Edge-native, perfect for Cloudflare Workers - Free tier: 9GB storage, 500M rows/month - Ultra-low latency with edge replicas 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Created 6 tables for cloud sync: - users: User accounts with email - magic_links: Passwordless auth tokens - devices: Registered sync devices - sync_log: Encrypted note changes - sync_cursors: Per-device sync position - subscriptions: Pro tier tracking 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Summary
@readied/apipackage with Hono backend for cloud syncTech Stack
API Endpoints
Auth
POST /auth/magic-link- Send magic link emailPOST /auth/verify- Verify token, get JWTPOST /auth/refresh- Refresh access tokenSync (Protected)
GET /sync?cursor=0- Pull changesPOST /sync- Push changesGET /sync/status- Get sync statusSubscription
POST /subscription/webhook- Stripe webhooksGET /subscription/status- Get plan statusTest plan
pnpm --filter @readied/api typecheck)pnpm --filter @readied/api dev)🤖 Generated with Claude Code